package excel

import (
	"bytes"
	"io"
	"reflect"

	"github.com/xuri/excelize/v2"
	"github.com/zeromicro/go-zero/core/logx"
)

type PeopleInfo struct {
	PeopleNo         string `ex:"head:工号;type:string;required;color:#0070C0"`
	PeopleName       string `ex:"head:姓名;type:string;required"`
	BirthDate        string `ex:"head:出生日期;type:date;omitempty"`
	EmploymentStatus string `ex:"head:在职状态;type:string;select:在职,离职"`
}

type Setting struct {
	Head      string
	Type      string
	Select    []string
	Required  bool
	OmitEmpty bool
	Color     string
}

// 解析第idx个字段的ex
func ParseEx(idx int, data any) *Setting {
	tp := reflect.ValueOf(data).Type().Elem().Elem() // 获得结构体的反射Type
	field := tp.Field(idx)
	exStr := field.Tag.Get("ex") // 获得tag中ex的值
	setting := &Setting{}
	// 下面可通过对exStr字符串进行切分，来组装Setting对象，较为简单，省略

	return setting
}

// ParseEx(0, []*PeopleInfo{{}})

// 写入第1行数据的第idx个字段
func WriteFirstRow(ef *excelize.File, idx int, data any) error {
	firstRow := reflect.ValueOf(data).Index(0).Elem() // 第1个数据的反射Value
	v := firstRow.Field(idx)                          // 第idx个字段的反射Value
	setting := ParseEx(idx, data)                     // 第idx个字段解析出来的ex信息

	// 处理omitempty
	if setting.OmitEmpty && v.IsZero() {
		return nil
	}

	var val any
	// 处理type
	switch setting.Type {
	case "string":
		val = v.String()
	}

	// Excel列号从1开始，所以列号是idx+1；行号从2开始，因为第1行要显示列名
	axis, err := excelize.CoordinatesToCellName(idx+1, 2)
	if err != nil {
		return err
	}

	// 将数据写入默认工作表Sheet1中axis坐标处的单元格
	return ef.SetCellValue("Sheet1", axis, val)
}

// ef := excelize.NewFile()
// WriteFirstRow(ef, 0, []*PeopleInfo{{PeopleNo: "test"}})
// ef.SaveAs("people_info.xlsx")

// 读取第1行数据的第idx列，假定idx从0开始，只有一个默认工作表Sheet1，数据从第2行开始
func ReadFirstRow(ef *excelize.File, idx int, holder any) error {
	rows, err := ef.GetRows("Sheet1") // 所有行
	if err != nil {
		return err
	}
	row := rows[1]

	tp := reflect.TypeOf(holder).Elem().Elem().Elem() // 结构体的类型
	val := reflect.New(tp)                            // 创建一个新的结构体对象

	field := val.Elem().Field(idx) // 第idx个字段的反射Value
	cellValue := row[idx]          // 第idx个字段对应的Excel数据
	field.SetString(cellValue)     // 将Excel数据保存到结构体对象的对应字段中

	listV := reflect.ValueOf(holder)
	listV.Elem().Set(reflect.Append(listV.Elem(), val)) // 将结构体对象添加到holder中

	return nil
}

// ef, _ := excelize.OpenFile("people_info.xlsx")
// holder := make([]*PeopleInfo, 0, 10)
// ReadFirstRow(ef, 0, &holder)

// 写入第1行数据
func StreamWriteFirstRow(sw *excelize.StreamWriter, data any) error {
	firstRow := reflect.ValueOf(data).Index(0).Elem() // 第1个数据的反射Value
	v := firstRow.Field(0)                            // 首个字段的反射Value
	style := &excelize.Style{
		Alignment: &excelize.Alignment{
			Horizontal: "left",
			Vertical:   "center",
		},
	}
	styleID, err := sw.File.NewStyle(style) // 创建样式
	if err != nil {
		return err
	}

	length := firstRow.NumField() // 结构体字段数量

	row := make([]interface{}, length) // 创建一个切片，表示一行数据
	row[0] = excelize.Cell{
		Value:   v.String(),
		StyleID: styleID,
	} // 这里只写入首个字段

	// 每一行都是从列号1开始；行号从2开始，因为假定第1行要显示列名
	axis, err := excelize.CoordinatesToCellName(1, 2)
	if err != nil {
		return err
	}

	// 流式写入行，并指定高度
	return sw.SetRow(axis, row, excelize.RowOpts{Height: 16})
}

// ef := excelize.NewFile()
// sw, _ := ef.NewStreamWriter("Sheet1")
// StreamWriteFirstRow(sw, []*PeopleInfo{{PeopleNo: "test"}})
// sw.Flush()
// ef.SaveAs("stream_people_info.xlsx")

// 大工作表的读取
// Excelize 库通过一些参数，来限制打开和读取工作薄时的内存使用。其中，WorksheetUnzipMemLimit 参数限制了 unzip 一个工作表时允许使用的最大内存，默认为 16 MB 。当一个工作表大小超过这一默认值时，此工作表的数据会 unzip 到操作系统的临时文件中。然而，当我们进行另存为操作时，这些临时文件的数据却被 Excelize 的相关函数忽略了。
// 我们可以通过增大 WorksheetUnzipMemLimit 参数来规避。这一参数的值，可根据具体业务场景来设置，最大可以设置为和 UnzipSizeLimit 参数相同，后者是打开整个工作簿时总的内存使用限制，默认为 16 GB 。

// 导出 Excel 文档
func Create(fileName string) (err error) {
	f := excelize.NewFile()
	// 创建一个工作表
	index := f.NewSheet("Sheet2")
	// 根据sheet和坐标，设置单元格的值
	// 在Sheet2表中A列第2行的格子里填入Hello world.
	f.SetCellValue("Sheet2", "A2", "Hello world.")
	f.SetCellValue("Sheet1", "B1", 100)
	f.SetCellValue("Sheet1", "B2", 100)

	// 设置工作簿的默认工作表
	f.SetActiveSheet(index)
	// 根据指定路径保存Excel文件
	if err = f.SaveAs(fileName); err != nil {
		logx.Errorw("excel error when saving", logx.Field("detail", err.Error()))
	}

	return
}

// 向Excel文件中追加内容
func Append(fileName string) (err error) {
	file, err := excelize.OpenFile(fileName)
	if err != nil {
		logx.Errorw("excel error when reading", logx.Field("detail", err.Error()))
		return
	}

	sheet := file.NewSheet("Sheet2")
	file.SetCellValue("Sheet2", "A2", "Hello world.")
	file.SetCellValue("Sheet1", "B1", 100)
	file.SetCellValue("Sheet1", "B2", 100)
	file.SetActiveSheet(sheet)
	if err = file.SaveAs(fileName); err != nil {
		logx.Errorw("excel error when saving", logx.Field("detail", err.Error()))
		return
	}

	return nil
}

// 读取Excel文档
func ReadFile(fileName string) (resp []string, err error) {
	f, err := excelize.OpenFile(fileName)
	if err != nil {
		logx.Errorw("excel error when opening", logx.Field("detail", err.Error()))
		return
	}

	defer func() {
		if err := f.Close(); err != nil {
			logx.Errorw("excel error when closing", logx.Field("detail", err.Error()))
		}
	}()

	// 获取工作表中指定单元格的值
	cell, err := f.GetCellValue("Sheet1", "B2")
	if err != nil {
		logx.Errorw("excel error when getting cell", logx.Field("detail", err.Error()))
		return
	}

	resp = append(resp, cell)

	// 获取 Sheet1 上所有单元格
	rows, err := f.GetRows("Sheet1")
	if err != nil {
		logx.Errorw("excel error when getting rows", logx.Field("detail", err.Error()))
		return
	}

	for _, row := range rows {
		for _, colCell := range row {
			resp = append(resp, colCell)
		}
	}

	return
}

// 解析Excel文件
func ReadFile(read io.Reader) (resp string) {
	file, err := excelize.OpenReader(read)
	if err != nil {
		logx.Errorw("excel error when reading file", logx.Field("detail", err.Error()))
		return
	}

	for _, sheet := range file.GetSheetList() {
		// rows是一个二维数组
		rows, _ := file.GetRows(sheet)
		for _, row := range rows {
			for _, cell := range row {
				resp += cell + "\n"
			}
		}
	}

	return
}

// 使用Http协议将数据转换为Excel文件并下载
func Download(fileName string) *bytes.Reader {
	file := excelize.NewFile()
	file.SetCellValue("Sheet1", "B1", 100)
	err := file.SaveAs(fileName)
	if err != nil {
		logx.Errorw("excel error when saving", logx.Field("detail", err.Error()))
		return nil
	}

	var buffer bytes.Buffer
	_ = file.Write(&buffer)

	return bytes.NewReader(buffer.Bytes())
}
